package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {


    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserAccountDetailMapper userAccountDetailMapper;

    @Override
    public void initUserAccount(String userId) {
        UserAccount userAccount = new UserAccount();
        userAccount.setUserId(Long.valueOf(userId));
        userAccount.setTotalAmount(new BigDecimal("10"));
        userAccount.setAvailableAmount(new BigDecimal("10"));
        this.baseMapper.insert(userAccount);
    }

    @Override
    public BigDecimal getAvailableAmount(Long userId) {
        UserAccount userAccount = this.getOne(new LambdaQueryWrapper<UserAccount>().eq(UserAccount::getUserId, userId));
        return userAccount.getAvailableAmount();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {
        //	检查key
        String key = "checkAndLock:" + accountLockVo.getOrderNo();
        //	数据key
        String dataKey = "account:lock:" + accountLockVo.getOrderNo();
        Boolean ifAbsent = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, accountLockVo.getOrderNo(), 10, TimeUnit.MINUTES);
        //判断是不是第一次请求
        if (Boolean.FALSE.equals(ifAbsent)) {
            //是就从缓存中获取数据
            String str = stringRedisTemplate.opsForValue().get(dataKey);
            if (!StringUtils.hasText(str)) {
                throw new GuiguException(ResultCodeEnum.FAIL);
            }
            return JSON.parseObject(str, AccountLockResultVo.class);
        }
        //	核对账户可用金额并锁定账户数据(悲观锁)；查询返回的是满足要求的账户
        UserAccount userAccount = this.baseMapper.check(accountLockVo.getUserId(), accountLockVo.getAmount());
        if (userAccount == null) {
            log.info("用户：{}，账户余额不足", accountLockVo.getUserId());
            this.stringRedisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
        }
        //锁定金额
        int lock = this.baseMapper.lock(accountLockVo.getUserId(), accountLockVo.getAmount());
        if (lock == 0) {
            log.info("账户余额锁定失败");
            this.stringRedisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }
        //	添加账户明细
        this.log(accountLockVo.getUserId(), "锁定：" + accountLockVo.getContent()
                , SystemConstant.ACCOUNT_TRADE_TYPE_LOCK, accountLockVo.getAmount()
                , "lock:" + accountLockVo.getOrderNo());

        //返回锁定对象
        AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
        accountLockResultVo.setUserId(accountLockVo.getUserId());
        accountLockResultVo.setAmount(accountLockVo.getAmount());
        accountLockResultVo.setContent(accountLockVo.getContent());
        // 如果账户锁定成功的情况下，需要缓存锁定信息到redis。以方便将来解锁账户金额 或者 减账户金额
        stringRedisTemplate.opsForValue().set(dataKey, JSON.toJSONString(accountLockResultVo)
                , 1, TimeUnit.HOURS);
        //	返回数据
        return accountLockResultVo;
    }

    @Override
    public void minus(String orderNo) {
        // 防止重复消费
        String key = "minus:" + orderNo;
        // 数据key
        String dataKey = "account:lock:" + orderNo;

        //业务去重，防止重复消费
        Boolean isExist = stringRedisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (!isExist) {
            return;
        }

        // 获取锁定库存的缓存信息
        String data = this.stringRedisTemplate.opsForValue().get(dataKey);
        if (StringUtils.isEmpty(data)) {
            return;
        }

        // 扣减账户金额
        AccountLockResultVo accountLockResultVo = JSONObject.parseObject(data, AccountLockResultVo.class);
        int minus = this.baseMapper.minus(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
        if (minus == 0) {
            //解除去重
            this.stringRedisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
        }
        //记录日志
        this.log(accountLockResultVo.getUserId(), accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_MINUS, accountLockResultVo.getAmount(), orderNo);

        // 解锁账户金额之后，删除锁定缓存。以防止重复解锁
        this.stringRedisTemplate.delete(dataKey);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlock(String orderNo) {
        // 防止重复消费
        String key = "unlock:" + orderNo;
        // 数据key
        String dataKey = "account:lock:" + orderNo;

        //业务去重，防止重复消费
        Boolean isExist = stringRedisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (!isExist) {
            return;
        }

        // 获取锁定库存的缓存信息
        String data = (String) this.stringRedisTemplate.opsForValue().get(dataKey);
        if (StringUtils.isEmpty(data)) {
            return;
        }

        // 解锁账户金额
        AccountLockResultVo accountLockResultVo = JSONObject.parseObject(data, AccountLockResultVo.class);
        // 调用解锁方法
        int unLock = this.baseMapper.unLock(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
        if (unLock == 0) {
            //解除去重
            this.stringRedisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
        }
        //记录日志
        this.log(accountLockResultVo.getUserId(), "解锁：" + accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK, accountLockResultVo.getAmount(), "unlock:" + orderNo);

        // 解锁账户金额之后，删除锁定缓存。以防止重复解锁
        this.stringRedisTemplate.delete(dataKey);
    }

    @Override
    public void add(Long userId, BigDecimal amount, String orderNo, String tradeType, String title)  {
        // 查看是否有当前对象
        long count = userAccountDetailMapper.selectCount(new LambdaQueryWrapper<UserAccountDetail>().eq(UserAccountDetail::getOrderNo, orderNo));
        if(count > 0) return;
        //添加账号金额
        this.baseMapper.add(userId, amount);
        //添加账户明细
        this.log(userId, title, tradeType, amount, orderNo);
    }

    @Override
    public IPage<UserAccountDetail> findUserRechargePage(Page<UserAccountDetail> pageParam, Long userId) {
        return userAccountDetailMapper.selectUserRechargePage(pageParam,userId);
    }

    @Override
    public IPage<UserAccountDetail> findUserConsumePage(Page<UserAccountDetail> pageParam, Long userId) {
        // 调用mapper 层方法
        return userAccountDetailMapper.selectUserConsumePage(pageParam, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkAndDeduct(AccountLockVo accountDeductVo) {
        try {
            //1.检查及扣减余额
            int count = this.baseMapper.checkAndDeduct(accountDeductVo.getUserId(), accountDeductVo.getAmount());
            //        int i = 1/0;
            if (count == 0) {
                throw new GuiguException(400, "账户余额不足！");
            }
            //2.新增账户变动日志
            this.log(
                    accountDeductVo.getUserId(),
                    accountDeductVo.getContent(),
                    SystemConstant.ACCOUNT_TRADE_TYPE_MINUS,
                    accountDeductVo.getAmount(),
                    accountDeductVo.getOrderNo()
            );
        } catch (GuiguException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 记录账户明细
     *
     * @param userId
     * @param title
     * @param tradeType
     * @param amount
     * @param orderNo
     */
    private void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
        //添加账户明细
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        //	添加数据
        userAccountDetailMapper.insert(userAccountDetail);
    }
}
